﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using WPFCABShell.Shell.Interface.Diagnostics;
using WPFCABShell.Shell.Interface.Services.Settings;
using Kent.Boogaart.HelperTrinity;
using Microsoft.Practices.ObjectBuilder;

namespace WPFCABShell.Shell.Services.Settings
{
	public sealed class IsolatedStorageSettingsService : ISettingsService
	{
		private readonly IDictionary<string, ISettings> _cache;

		public ISettings this[string name]
		{
			get
			{
				return GetSettings(name);
			}
		}

		public IsolatedStorageSettingsService()
		{
			_cache = new Dictionary<string, ISettings>();
		}

		public ISettings GetSettings(string name)
		{
			ArgumentHelper.AssertNotNull(name, "name");
			ISettings settings;

			if (!_cache.TryGetValue(name, out settings))
			{
				lock (_cache)
				{
					if (!_cache.TryGetValue(name, out settings))
					{
						settings = new Settings(name);
						_cache[name] = settings;
					}
				}
			}

			return settings;
		}

		private sealed class Settings : ISettings
		{
			private readonly string _name;
			private readonly SurrogateSelector _surrogateSelector;
			private readonly BinaryFormatter _binaryFormatter;
			private IDictionary<string, object> _settings;
			private static readonly Log _log = Log.CreateForType(typeof(Settings));

			public Settings(string name)
			{
				Debug.Assert(name != null);
				ExceptionHelper.ThrowIf(name.IndexOfAny(Path.GetInvalidFileNameChars()) != -1, "InvalidName");
				ExceptionHelper.ThrowIf(name.IndexOf(Path.PathSeparator) != -1, "InvalidName");
				_name = name;
				_surrogateSelector = new SurrogateSelector();
				_binaryFormatter = new BinaryFormatter();
				_binaryFormatter.TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesWhenNeeded;
				_binaryFormatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
				_binaryFormatter.SurrogateSelector = _surrogateSelector;
			}

			public object this[string key]
			{
				get
				{
					return Get(key);
				}
				set
				{
					Add(key, value);
				}
			}

			public void AddSerializationSurrogate(Type type, ISerializationSurrogate surrogate)
			{
				ArgumentHelper.AssertNotNull(type, "type");
				ArgumentHelper.AssertNotNull(surrogate, "surrogate");
				ExceptionHelper.ThrowIf(_settings != null, "AlreadyLoaded");
				_surrogateSelector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), surrogate);
			}

			public bool Contains(string key)
			{
				ArgumentHelper.AssertNotNull(key, "key");
				Load();
				return _settings.ContainsKey(key);
			}

			public void Add(string key, object value)
			{
				ArgumentHelper.AssertNotNull(key, "key");
				Load();
				_settings[key] = value;
			}

			public object Get(string key)
			{
				ArgumentHelper.AssertNotNull(key, "key");
				Load();
				return _settings[key];
			}

			public object GetClone(string key)
			{
				object data = Get(key);

				if (data == null)
				{
					return null;
				}

				using (MemoryStream memoryStream = new MemoryStream())
				{
					_binaryFormatter.Serialize(memoryStream, data);
					memoryStream.Position = 0;
					return _binaryFormatter.Deserialize(memoryStream);
				}
			}

			public void Remove(string key)
			{
				ArgumentHelper.AssertNotNull(key, "key");
				Load();
				_settings.Remove(key);
			}

			public void Save()
			{
				try
				{
					using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForAssembly())
					using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(_name, FileMode.Create, isoFile))
					{
						_binaryFormatter.Serialize(isoStream, _settings);
					}
				}
				catch (Exception ex)
				{
					_log.Exception(LogLevel.Information, ex);
					//probably trying to serialize something that isn't serializable
					throw;
				}
			}

			private void Load()
			{
				if (_settings == null)
				{
					try
					{
						using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForAssembly())
						using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(_name, FileMode.OpenOrCreate, isoFile))
						{
							if (isoStream.Length > 0)
							{
								_settings = _binaryFormatter.Deserialize(isoStream) as IDictionary<string, object>;
							}
						}
					}
					catch (Exception ex)
					{
						_log.Exception(LogLevel.Information, ex);
					}

					if (_settings == null)
					{
						_settings = new Dictionary<string, object>();
					}
				}
			}
		}
	}
}